package main

/*
#cgo LDFLAGS: -lm
#include <stdint.h>
#include <stdlib.h> // 包含 free 函数的声明
*/
import "C"
import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"unsafe"

	"github.com/emmansun/gmsm/sm2"
)

// 错误码定义
const (
	Success               = 0  // 成功
	ErrorInvalidPubKey    = -1 // 无效的公钥
	ErrorEncryptionFail   = -2 // 加密失败
	ErrorInvalidInput     = -3 // 输入无效
	ErrorInvalidInputLen  = -4 // 输入长度无效
	ErrorMemoryAllocation = -5 // 内存分配失败
	ErrorInvalidPrivKey   = -6 // 无效的私钥
	ErrorDecryptionFail   = -7 // 解密失败
)

// 密文拼接顺序定义
const (
	C1C3C2 = 0 // 默认拼接顺序
	C1C2C3 = 1 // 另一种拼接顺序
)

//export SM2Encrypt
//export @CDECL
func SM2Encrypt(pubKeyHex *C.char, plaintext *C.char, order C.int, ciphertext **C.uint8_t, ciphertextLen *C.int) C.int {
	/*
		参数说明：
		1. pubKeyHex (*C.char)：输入参数，表示公钥的十六进制字符串形式。
		2. plaintext (*C.char)：输入参数，表示需要加密的明文字符串。
		3. order (C.int)：输入参数，表示密文的拼接顺序，0 表示 C1C3C2，1 表示 C1C2C3。
		4. ciphertext (**C.uint8_t)：输出参数，返回加密后的密文数据指针。
		5. ciphertextLen (*C.int)：输出参数，返回加密后密文的长度。

		返回值：
		- 成功时返回 Success (0)。
		- 如果公钥无效，返回 ErrorInvalidPubKey (-1)。
		- 如果加密失败，返回 ErrorEncryptionFail (-2)。
		- 如果输入参数无效，返回 ErrorInvalidInput (-3)。
		- 如果输入长度无效，返回 ErrorInvalidInputLen (-4)。
		- 如果内存分配失败，返回 ErrorMemoryAllocation (-5)。
		- 如果密文拼接顺序无效，返回 ErrorInvalidOrder (-6)。
	*/

	// 检查输入参数是否为空
	if pubKeyHex == nil || plaintext == nil || ciphertext == nil || ciphertextLen == nil {
		fmt.Println("输入参数无效")
		return ErrorInvalidInput
	}

	// 将 C 字符串转换为 Go 字符串
	pubKeyHexGo := C.GoString(pubKeyHex)
	plainTextGo := C.GoString(plaintext)

	// 检查输入长度
	if len(pubKeyHexGo) == 0 || len(plainTextGo) == 0 {
		fmt.Println("输入参数长度无效")
		return ErrorInvalidInputLen
	}

	// 检查拼接顺序是否合法,如果不合法，直接采用默认拼接顺序
	if order != C1C3C2 && order != C1C2C3 {
		order = C.int(C1C3C2)
	}

	// 将公钥从十六进制字符串转换为字节数组
	pubKeyBytes, err := hex.DecodeString(pubKeyHexGo)
	if err != nil {
		fmt.Println("公钥解析失败:", err)
		return ErrorInvalidPubKey
	}

	// 解析公钥
	pubKey, err := sm2.NewPublicKey(pubKeyBytes)
	if err != nil || pubKey == nil {
		fmt.Println("公钥无效")
		return ErrorInvalidPubKey
	}

	// 创建加密选项
	var opts *sm2.EncrypterOpts
	if order == C1C2C3 {
		opts = sm2.NewPlainEncrypterOpts(sm2.MarshalUncompressed, sm2.C1C2C3)
	} else {
		opts = sm2.NewPlainEncrypterOpts(sm2.MarshalUncompressed, sm2.C1C3C2)
	}

	// 使用公钥加密
	ciphertextBytes, err := sm2.Encrypt(rand.Reader, pubKey, []byte(plainTextGo), opts)
	if err != nil {
		fmt.Println("加密失败:", err)
		return ErrorEncryptionFail
	}

	// 分配内存并将密文传递给 C
	cBytes := C.CBytes(ciphertextBytes)
	if cBytes == nil {
		fmt.Println("内存分配失败")
		return ErrorMemoryAllocation
	}

	// 设置输出参数
	*ciphertext = (*C.uint8_t)(cBytes)
	*ciphertextLen = C.int(len(ciphertextBytes))

	// 返回成功状态
	return Success
}

//export FreeCiphertext
//export @CDECL
func FreeCiphertext(out *C.uint8_t) {
	/*
		参数说明：
		1. out (*C.uint8_t)：输入参数，表示需要释放的密文数据指针。

		功能：
		- 释放由 SM2Encrypt 分配的密文内存，防止内存泄漏。
	*/

	// 释放分配的内存
	if out != nil {
		C.free(unsafe.Pointer(out))
	}
}

//export SM2Decrypt
//export @CDECL
func SM2Decrypt(privKeyHex *C.char, ciphertext *C.uint8_t, ciphertextLen C.int, order C.int, plaintext **C.char, plaintextLen *C.int) C.int {
	/*
		参数说明：
		1. privKeyHex (*C.char)：输入参数，表示私钥的十六进制字符串形式。
		2. ciphertext (*C.uint8_t)：输入参数，表示需要解密的密文数据指针。
		3. ciphertextLen (C.int)：输入参数，表示密文的长度。
		4. order (C.int)：输入参数，表示密文的拼接顺序，0 表示 C1C3C2，1 表示 C1C2C3。
		5. plaintext (**C.char)：输出参数，返回解密后的明文字符串。
		6. plaintextLen (*C.int)：输出参数，返回解密后的明文长度。

		返回值：
		- 成功时返回 Success (0)。
		- 如果私钥无效，返回 ErrorInvalidPrivKey (-6)。
		- 如果解密失败，返回 ErrorDecryptionFail (-7)。
		- 如果输入参数无效，返回 ErrorInvalidInput (-3)。
		- 如果输入长度无效，返回 ErrorInvalidInputLen (-4)。
	*/

	// 检查输入参数是否为空
	if privKeyHex == nil || ciphertext == nil || plaintext == nil || plaintextLen == nil {
		fmt.Println("输入参数无效")
		return ErrorInvalidInput
	}

	// 检查密文长度是否有效
	if ciphertextLen <= 0 {
		fmt.Println("密文长度无效")
		return ErrorInvalidInputLen
	}

	// 检查拼接顺序是否合法,如果不合法，直接采用默认拼接顺序
	if order != C1C3C2 && order != C1C2C3 {
		order = C.int(C1C3C2)
	}

	// 将 C 字符串转换为 Go 字符串
	privKeyHexGo := C.GoString(privKeyHex)

	// 将私钥从十六进制字符串转换为字节数组
	privKeyBytes, err := hex.DecodeString(privKeyHexGo)
	if err != nil {
		fmt.Println("私钥解析失败:", err)
		return ErrorInvalidPrivKey
	}

	// 解析私钥
	privKey, err := sm2.NewPrivateKey(privKeyBytes)
	if err != nil || privKey == nil {
		fmt.Println("私钥无效")
		return ErrorInvalidPrivKey
	}

	// 将密文从 C 指针转换为 Go 字节数组
	ciphertextBytes := C.GoBytes(unsafe.Pointer(ciphertext), ciphertextLen)

	//创建解密选项
	var opts *sm2.DecrypterOpts
	if order == C1C2C3 {
		opts = sm2.NewPlainDecrypterOpts(sm2.C1C2C3)
	} else {
		opts = sm2.NewPlainDecrypterOpts(sm2.C1C3C2)
	}

	// 使用私钥解密，默认sm2.Decrypt无法使用c1c2c3,使用私钥解密
	plaintextBytes, err := privKey.Decrypt(rand.Reader, ciphertextBytes, opts)
	//plaintextBytes, err := sm2.Decrypt(privKey, ciphertextBytes)
	if err != nil {
		fmt.Println("解密失败:", err)
		return ErrorDecryptionFail
	}

	// 分配内存并将明文传递给 C
	plainTextC := C.CString(string(plaintextBytes))
	if plainTextC == nil {
		fmt.Println("内存分配失败")
		return ErrorMemoryAllocation
	}

	// 设置输出参数
	*plaintext = plainTextC
	*plaintextLen = C.int(len(plaintextBytes)) // 返回解密明文的长度

	// 返回成功状态
	return Success
}

//export FreePlaintext
//export @CDECL
func FreePlaintext(out *C.char) {
	/*
		参数说明：
		1. out (*C.char)：输入参数，表示需要释放的明文数据指针。

		功能：
		- 释放由 SM2Decrypt 分配的明文内存，防止内存泄漏。
	*/

	// 释放分配的内存
	if out != nil {
		C.free(unsafe.Pointer(out))
	}
}

func main() {
	// 测试用的公钥和私钥（十六进制字符串）
	pubKeyHex := "04742cd307424a4d12e56fbaeaf38c8d2837b4d7c9282d706f29e9a9b95eea343fd45d37378a6fa19ed330ffcd7fbf722a6cef79b69a9e2eec780db52d55882f34"
	privKeyHex := "2c3d34d41bb58134ef4bba9c25da8956949c12ab0f15f9d3fa8c75912b6c0db2"

	plaintext := "admin"

	// 准备输出参数
	var ciphertext *C.uint8_t
	var ciphertextLen C.int

	// 分配 C 字符串
	pubKeyHexC := C.CString(pubKeyHex)
	defer C.free(unsafe.Pointer(pubKeyHexC))

	plaintextC := C.CString(plaintext)
	defer C.free(unsafe.Pointer(plaintextC))

	privKeyHexC := C.CString(privKeyHex)
	defer C.free(unsafe.Pointer(privKeyHexC))

	// 测试 C1C3C2 拼接顺序
	fmt.Println("测试 C1C3C2 拼接顺序:")
	errorCode := SM2Encrypt(pubKeyHexC, plaintextC, C.int(C1C3C2), &ciphertext, &ciphertextLen)
	if errorCode == Success {
		ciphertextGo := C.GoBytes(unsafe.Pointer(ciphertext), ciphertextLen)
		fmt.Println("加密成功，密文:", hex.EncodeToString(ciphertextGo))

		// 测试解密
		var decryptedPlaintext *C.char
		var decryptedPlaintextLen C.int
		fmt.Println("\n测试解密 (C1C3C2 拼接顺序):")
		errorCode = SM2Decrypt(privKeyHexC, ciphertext, ciphertextLen, C.int(C1C3C2), &decryptedPlaintext, &decryptedPlaintextLen)
		if errorCode == Success {
			decryptedPlaintextGo := C.GoStringN(decryptedPlaintext, decryptedPlaintextLen) // 使用 GoStringN 提取指定长度的字符串
			fmt.Println("解密成功，明文:", decryptedPlaintextGo)
			fmt.Println("解密明文长度:", decryptedPlaintextLen)
			FreePlaintext(decryptedPlaintext)
		} else {
			fmt.Println("解密失败，错误码:", errorCode)
		}

		//释放密文内存
		FreeCiphertext(ciphertext)
	} else {
		fmt.Println("加密失败，错误码:", errorCode)
	}

	// 测试 C1C2C3 拼接顺序
	fmt.Println("\n测试 C1C2C3 拼接顺序:")
	errorCode = SM2Encrypt(pubKeyHexC, plaintextC, C.int(C1C2C3), &ciphertext, &ciphertextLen)
	if errorCode == Success {
		ciphertextGo := C.GoBytes(unsafe.Pointer(ciphertext), ciphertextLen)
		fmt.Println("加密成功，密文:", hex.EncodeToString(ciphertextGo))

		// 测试解密
		var decryptedPlaintext *C.char
		var decryptedPlaintextLen C.int
		fmt.Println("\n测试解密 (C1C2C3 拼接顺序):")
		errorCode = SM2Decrypt(privKeyHexC, ciphertext, ciphertextLen, C.int(C1C2C3), &decryptedPlaintext, &decryptedPlaintextLen)
		if errorCode == Success {
			decryptedPlaintextGo := C.GoStringN(decryptedPlaintext, decryptedPlaintextLen) // 使用 GoStringN 提取指定长度的字符串
			fmt.Println("解密成功，明文:", decryptedPlaintextGo)
			fmt.Println("解密明文长度:", decryptedPlaintextLen)
			FreePlaintext(decryptedPlaintext)
		} else {
			fmt.Println("解密失败，错误码:", errorCode)
		}

		//释放密文内存
		FreeCiphertext(ciphertext)
	} else {
		fmt.Println("加密失败，错误码:", errorCode)
	}

}
